use ast::{Expr, Name, Statement};
use visit::Visitor;

// The data we will visit.
pub mod ast {
    pub struct Name {
        pub value: String,
    }

    pub enum Expr {
        IntLit(i64),
        Add(Box<Expr>, Box<Expr>),
        Sub(Box<Expr>, Box<Expr>),
    }

    pub enum Statement {
        Expr(Expr),
        Let(Name, Expr),
    }
}

// The abstract visitor.
pub mod visit {
    use super::ast::{Expr, Name, Statement};

    pub trait Visitor<T> {
        fn visit_name(&mut self, n: &Name) -> T;

        fn visit_statement(&mut self, s: &Statement) -> T;

        fn visit_expr(&mut self, e: &Expr) -> T;
    }
}

pub struct Interpreter;

impl Visitor<i64> for Interpreter {
    fn visit_name(&mut self, _name: &Name) -> i64 {
        panic!("Cannot visit Name")
    }

    fn visit_statement(&mut self, statement: &Statement) -> i64 {
        match *statement {
            Statement::Expr(ref expr) => self.visit_expr(expr),
            Statement::Let(..) => unimplemented!(),
        }
    }

    fn visit_expr(&mut self, expr: &Expr) -> i64 {
        match *expr {
            Expr::IntLit(val) => val,
            Expr::Add(ref lhs, ref rhs) => self.visit_expr(lhs) + self.visit_expr(rhs),
            Expr::Sub(ref lhs, ref rhs) => self.visit_expr(lhs) - self.visit_expr(rhs),
        }
    }
}

fn main() {}

#[cfg(test)]
mod tests {
    use super::ast::{Expr, Name, Statement};
    use super::visit::Visitor;
    use super::Interpreter;

    #[test]
    #[should_panic(expected = "Cannot visit Name")]
    fn name() {
        let mut interpreter = Interpreter {};
        let name = Name {
            value: "x".to_string(),
        };

        interpreter.visit_name(&name);
    }

    #[test]
    fn expr_as_int() {
        let mut interpreter = Interpreter {};
        let expr = Expr::IntLit(1);

        let res = interpreter.visit_expr(&expr);

        assert_eq!(1, res);
    }

    #[test]
    fn expr_as_add() {
        let mut interpreter = Interpreter {};
        let expr = Expr::Add(Box::new(Expr::IntLit(1)), Box::new(Expr::IntLit(2)));

        let res = interpreter.visit_expr(&expr);

        assert_eq!(3, res);
    }

    #[test]
    fn expr_as_sub() {
        let mut interpreter = Interpreter {};
        let expr = Expr::Sub(Box::new(Expr::IntLit(1)), Box::new(Expr::IntLit(2)));

        let res = interpreter.visit_expr(&expr);

        assert_eq!(-1, res);
    }

    #[test]
    #[should_panic(expected = "not implemented")]
    fn statement_as_let() {
        let mut interpreter = Interpreter {};
        let statement = Statement::Let(
            Name {
                value: "x".to_string(),
            },
            Expr::IntLit(1),
        );

        interpreter.visit_statement(&statement);
    }

    #[test]
    fn statement_as_int_expr() {
        let mut interpreter = Interpreter {};
        let statement = Statement::Expr(Expr::IntLit(1));

        let res = interpreter.visit_statement(&statement);

        assert_eq!(1, res);
    }

    #[test]
    fn statement_as_add_expr() {
        let mut interpreter = Interpreter {};
        let statement = Statement::Expr(Expr::Add(
            Box::new(Expr::IntLit(1)),
            Box::new(Expr::IntLit(2)),
        ));

        let res = interpreter.visit_statement(&statement);

        assert_eq!(3, res);
    }

    #[test]
    fn statement_as_sub_expr() {
        let mut interpreter = Interpreter {};
        let statement = Statement::Expr(Expr::Sub(
            Box::new(Expr::IntLit(1)),
            Box::new(Expr::IntLit(2)),
        ));

        let res = interpreter.visit_statement(&statement);

        assert_eq!(-1, res);
    }
}
